#!/usr/bin/env node
const cp = require('child_process');
const fs = require('fs');
const moment = require('moment');

const {
    week, month, date, colors,
    plate: ori_plate, shape,
} = require('./data.json');

function fill(num, type, plate) {
    const arr = ({ week, month, date })[type];
    if (!arr) throw new Error('fill params type Exception');

    const val = arr[num - 1];
    const max = plate[0].length;

    const row = Math.floor(val / max);
    const col = val % max;

    if (typeof plate[row][col] !== 'number') {
        throw new Error('fill params num Exception');
    }
    if (plate[row][col] !== 0) {
        throw new Error('fill params num has been assigned a value ');
    }

    plate[row][col] = 1;
};

function init(name, shapeList, plate, queue) {
    const max = plate[0].length;
    // 循环棋盘的每个格子，作为起点
    for (let i = 0; i < max; i += 1 ) {
        for (let j = 0; j< plate.length; j += 1) {
            let itemIndex = -1;
            for (const shapeItem of shapeList) {
                itemIndex++;
                const newPlate = setShape(plate, shapeItem, i, j, itemIndex, name);
                if (!newPlate) continue;
                
                // if (name === 'NO4') {
                //     console.log('--->', i, j, itemIndex);
                // }

                queue.push({
                    plate: newPlate,
                    path: [{ i, j, k: itemIndex, name }]
                });
            }
        }
    }
}

function setShape(plate, shapeItem, plateRowIndex, plateColIndex, itemIndex, name) {
    // 复制棋盘，避免无效时污染数据
    const copyPlate = JSON.parse(JSON.stringify(plate));

    const maxCol = copyPlate[0].length - 1; // 棋盘最大列索引
    const maxRow = copyPlate.length - 1; // 棋盘最大行索引

    let i = -1;
    for (const itemRow of shapeItem) { // 循环图形行
        i++;
        let j = -1;

        for (const itemCell of itemRow) { // 循环图形格子
            j++;
            if (itemCell === 0) continue; // 图形格子为0，忽略

            const x = plateRowIndex + i;
            const y = plateColIndex + j;

            if (x > maxRow || y > maxCol) { // 图形格子在棋盘上越界，返回无效
                // console.log('--->', i, j, plateRowIndex, plateColIndex, itemIndex);
                return false;
            }

            if (copyPlate[x][y] === 1) { // 棋盘当前格子已被设置，返回无效
                // console.log('===>', i, j, plateRowIndex, plateColIndex, itemIndex);
                return false;
            }

            // if (name === 'NO4'&& i == 1) console.log('--->', i, j, x, y, copyPlate[x][y]);

            copyPlate[x][y] = 1;
        }
    }

    // 剪枝优化
    // if (minAreaOfIsland(copyPlate) < 3) {
    //     return false;
    // }

    
    // console.log('===>', JSON.stringify(copyPlate), shapeItem);
    // 该图形可放入棋盘，则改写棋盘
    return copyPlate;
}

/* function minAreaOfIsland(grid) {
    const copyGrid = JSON.parse(JSON.stringify(grid));

    let i= -1;
    let min = 56; // 7 * 8，棋盘总格子数

    for (const row of copyGrid) {
        i++;
        let j = -1;

        for (cel of row) {
            j++;
            if (copyGrid[i][j] === 0) {
                min = Math.min(dfs(copyGrid, i, j), min);
            }
        }
    }

    return min;
};

// 盘子，行，列，当前数量
function dfs(grid, i, j, count=0) {
    grid[i][j] = 1; // 已统计过的，设为海洋
    count += 1;

    const list = [
        [i-1, j],
        [i+1, j],
        [i, j-1],
        [i, j+1],
    ];
    
    while(list.length) {
        const item = list.shift();
        if (grid[item[0]]?.[item[1]] !== 0) continue;
        count = dfs(grid, item[0], item[1], count);
    }

    return count;
} */

async function strategy(sortList) {
    // console.log('->', sortList);
    const list1 = [...sortList]; // 初始正向
    const list2 = [...sortList].reverse(); // 逆向

    const list3 = [sortList[5]]; // 中间开头, 从左到右
    list3.push(...sortList.slice(0, 5));
    list3.push(...sortList.slice(6, 11));

    const list4 = [sortList[5]]; // 中间开头, 从右到左
    list4.push(...sortList.slice(6, 11).reverse());
    list4.push(...sortList.slice(0, 5).reverse());

    const generateRadomList = () => {
        const list = [...sortList];
        const res = [];
        while(list.length) {
            const len = list.length;
            const index = Math.floor(Math.random() * len);
            res.push(list.splice(index, 1));
        }
        return res;
    };

    const list5 = generateRadomList();
    const list6 = generateRadomList();
    const list7 = generateRadomList();
    const list8 = generateRadomList();
    const list9 = generateRadomList();

    const keyList = [
        list1, list2, list3, list4, list5,
        list6, list7, list8, list9,
    ];
    const promiseList = [];
    const childList = [];

    let index = 0;
    while(index < keyList.length) {
        const promise = spawn(keyList, index, childList);
        promiseList.push(promise);
        index++;
    }

    try {
        const res = await Promise.any(promiseList);
        // console.log('success', childList.map(v => v.pid));
        childList.forEach((child, i) => {
            if (i !== res.index) {
                child.kill('SIGINT');
            }
        });

        return res.data;
    } catch (err) {
        console.log('error', err);
        process.exit(0);
    }
}

async function spawn(list, index, childList) {
    const promise = new Promise((resolve, reject) => {
        const strArg = JSON.stringify(list[index]);
        const child = cp.spawn('node', ['./exec.js', strArg], {
            cwd: __dirname,
            // stdio: 'inherit'
        });
        childList[index] = child;

        let res = '';
        let err = '';

        child.stdout.on('data', data => {
            res += data.toString();
        });
        child.stderr.on('data', data => {
            err += data.toString();
        });
        child.on('exit', code => {
            if (code === 0) return resolve({ index, data: res });
            reject(err);
        });
    });

    return promise;
}

function beep() {
    cp.execSync(`rundll32 user32.dll,MessageBeep`);
}

function getDate() {
    const params = process.argv[2].split('.').map(v => Number(v));
    const m = params[0] < 10 ? `0${params[0]}` : params[0];
    const d = params[1] < 10 ? `0${params[1]}` : params[1];
    const w = moment(`${moment().format('YYYY')}-${m}-${d}`).day();

    params[2] = w === 0 ? 7 : w;
    return params;
}

async function main() {
    const DATE = getDate();
    const t = Date.now();
    const plate = ori_plate;

    fill(DATE[0], 'month', plate);
    fill(DATE[1], 'date', plate);
    fill(DATE[2], 'week', plate);
    fs.writeFileSync(`${__dirname}/_plate.json`, JSON.stringify(plate), 'utf8');

    const _queue = [];
    const shapes = Object.keys(shape);
    for (const name of shapes) {
        init(name, shape[name], ori_plate, _queue);
    }
    console.log('计算图形块摆放可能性耗时: ', (Date.now() - t), 'ms');

    const cache = {};
    const json = {};
    
    for (const v of _queue) {
        if (!cache[v.path[0].name]) {
            cache[v.path[0].name] = [v];
            json[v.path[0].name] = 1;
        } else {
            cache[v.path[0].name].push(v);
            json[v.path[0].name] += 1;
        }
    }
    fs.writeFileSync(`${__dirname}/_cache.json`, JSON.stringify(cache), 'utf8');

    // 排序所有块摆放可能性，从低到高
    const list = Object.keys(json)
        .map(k => ({ k, v: json[k] }))
        .sort((a, b) => a.v - b.v)
        .map(v => {
            // console.log(v);
            return v.k;
        });
    
    const result = await strategy(list);
    console.log(`求解${DATE[0]}月${DATE[1]}日星期${DATE[2]}总耗时:`, (Date.now() - t), 'ms');
    
    const con = fs.readFileSync(`${__dirname}/_index.html`, 'utf8');
    const html = con
        .replace('const plate = [];', `const plate = ${JSON.stringify(plate)};`)
        .replace('const paths = [];', `const paths = ${result};`)
        .replace('const colors = [];', `const colors = ${JSON.stringify(colors)};`)
        .replace('const shapes = {};', `const shapes = ${JSON.stringify(shape)};`);
    
    fs.writeFileSync(`${__dirname}/index.html`, html, 'utf8');
    cp.exec(`open -a "/Applications/Google Chrome.app" ${__dirname}/index.html`);

    // console.log('\n');
    // console.log(JSON.parse(result).map(v => `${v.name}: ${v.i}, ${v.j}, ${v.k}`).join('\n'));
}

main();


